<?php
/**
 * Created by PhpStorm.
 * User: meng
 * Date: 2023/9/6
 * Time: 10:23
 */

namespace app\middleware;

use app\admin\service\MenuService;
use Doctrine\Common\Annotations\AnnotationReader;
use lib\annotations\Permission;
use lib\exception\AuthException;
use lib\exception\ForbiddenException;
use model\AdminPermissionUserModel;
use model\AdminUserModel;
use ReflectionClass;
use ReflectionException;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use Tinywan\Jwt\Exception\JwtTokenException;
use Tinywan\Jwt\Exception\JwtTokenExpiredException;
use Tinywan\Jwt\JwtToken;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;

class AdminPermissionAuth implements MiddlewareInterface
{
    /**
     * @param \support\Request $request
     * @param callable $handler
     * @return Response
     * @throws ReflectionException
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function process(Request $request, callable $handler) : Response {
        $reflectionClass = new ReflectionClass($request->controller);
        $method = $reflectionClass->getMethod($request->action);
        $hasAnnotation = false;
        // 可以在注解中指定接口route, 如果未指定则自动获取路由定义的name
        $permissionName = null;
        if (method_exists($method, 'getAttributes') && version_compare(PHP_VERSION, '8.0.0', 'ge')) {
            $annotations = $method->getAttributes(Permission::class);
            if (!empty($annotations)) {
                $annotation = $annotations[0];
                $hasAnnotation = true;
                /**
                 * phpstorm 关闭下一行语法检查. 在php7下php8版本语法检查
                 * @noinspection ALL
                 */
                $args = $annotation->getArguments();
                if (!empty($args) && !empty($args['value'])) {
                    $permissionName = $args['value'];
                }
            }
        }
        if (!$hasAnnotation) {
            $reader = new AnnotationReader();
            $annotation = $reader->getMethodAnnotation($method, Permission::class);
            if (!empty($annotation)) {
                $hasAnnotation = true;
                if (!empty($annotation->value)) {
                    $permissionName = $annotation->value;
                }
            }
        }
        if ($hasAnnotation) {
            try {
                JwtToken::verify();
                $userid = JwtToken::getCurrentId();
                $request::macro('adminId', function () use ($userid) {
                    return $userid;
                });
            } catch (JwtTokenException $e) {
                throw new AuthException();
            } catch (JwtTokenExpiredException $e) {
                throw new AuthException('令牌过期', 401002);
            }
            $user = make(AdminUserModel::class)->hidden(['password', 'delete_time'])->findUser($userid);
            if ($user['status'] != 1) {
                throw new ForbiddenException('账号异常, 请联系管理员', 403002);
            }
            $request::macro('adminUser', function () use ($user): AdminUserModel {
                return $user;
            });
            $requestMethod = $request->method();
            if (empty($permissionName)) {
                $permissionName = $request->route->getPath();
            }
            // 如果是超级管理员直接放行
            $permissions = make(AdminPermissionUserModel::class)->getUserPermission($userid);
            if (empty($permissions)) {
                throw new ForbiddenException('未分配权限, 请联系管理员', 403002);
            }
            $permissionIds = array_column($permissions, 'id');
            if (!in_array(1, $permissionIds)) {
                // 判断菜单中是否有该请求
                $menuService = make(MenuService::class);
                $menus = $menuService->menuList();
                $currentMenu = array_values(array_filter($menus, function ($item) use ($requestMethod, $permissionName) {
                    return strtolower($item['api_method']) == strtolower($requestMethod) && strtolower($item['api_path']) == strtolower($permissionName);
                }));
                // 如果菜单中存在
                if (!empty($currentMenu)) {
                    $accessMenus = $menuService->permissionMenuList($permissionIds);
                    $accessMenuIds = array_column($accessMenus, 'id');
                    $currentMenuIds = array_column($currentMenu, 'id');
                    // 取交集
                    $access = array_intersect($accessMenuIds, $currentMenuIds);
                    if (empty($access)) {
                        throw new ForbiddenException('无权限访问', 403001);
                    }
                }
            }
        }
        return $handler($request);
    }
}
